//==================================================================
/// road.cpp
///
/// Created by Davide Pasca - 2009/10/5
/// See the file "license.txt" that comes with this project for
/// copyright info. 
//==================================================================

#include "stdafx.h"
#include "road.h"

//==================================================================
road::road( transformer *pXFormer )
{
	_x = 0;
	mScreenWd = 0;
	mScreenHe = 0;
	_lane_w = 0;
	_road_w = 0;
	_strip_h = 0;

	mpXFormer = pXFormer;

	colors_set(
		color( 70, 70, 70 ),
		color( 250, 250, 250 ),
		color( 20, 20, 20 ),
		color( 220, 220, 5 ),
		color( 200, 100, 10 ),
		color( 128, 140, 180 ) );

	mpoRdSec = new SectorList();

	for (size_t i=0; i < MAX_SIDE_SPRT; ++i)
	{
		mpSprtsLSec[i] = new SectorList();
		mpSprtsRSec[i] = new SectorList();
	}

	mSPatches.resize( VIEW_PATCHES+1 );
	mWPatches.resize( VIEW_PATCHES+1 );

	mpVList.resize( VIEW_PATCHES*4+3 );
	for (size_t i=0; i < mpVList.size(); ++i)
		mpVList[i] = 0;
}

//==================================================================
void road::Resize( U32 w, U32 h, U32 lane_w, U32 strip_h )
{
	mScreenWd = w;
	mScreenHe = h;
	_lane_w = lane_w;
	_road_w = _lane_w * 4;
	_strip_h = strip_h;

	mFarSectorY = mpXFormer->_hf_he;

	mpXFormer->SetScreenHe( mScreenHe * 120 / 100 );
	mpXFormer->offy_set( -_strip_h * 135 / 100 );
}

//==================================================================
road::~road()
{
	for (size_t i=0; i < MAX_SIDE_SPRT; ++i)
	{
		DSAFE_DELETE( mpSprtsLSec[i] );
		DSAFE_DELETE( mpSprtsRSec[i] );
	}
}

//==================================================================
void road::sector_add( int lanes, int vangle, int angle, int len )
{
	SectorDef	def;
	def.len		= len;
	def.step	= 2;
	def.angle	= angle;
	def.vangle	= vangle;
	def.lanes	= lanes;

	mpoRdSec->add( def );
}

//==================================================================
SectorList *road::get_sector( int sec_type )
{
	switch ( sec_type )
	{
	case L1: return mpSprtsLSec[0];
	case R1: return mpSprtsRSec[0];
	case L2: return mpSprtsLSec[1];
	case R2: return mpSprtsRSec[1];
	case L3: return mpSprtsLSec[2];
	case R3: return mpSprtsRSec[2];
	}

	return NULL;
}

//==================================================================
void road::colors_set(
			  const color &road,
			  const color &line,
			  const color &edge_a,
			  const color &edge_b,
			  const color &side,
			  const color &fog
			)
{
	_road_col_lt = col_mod( road, 1.015f );
	_road_col_dk = col_mod( road,  .985f );

	_line_col = line;
	_line_mip_col = color::make_inpt( road, line, 0.5f );

	_edge_a_col = edge_a;
	_edge_b_col = edge_b;

	_fog_col = fog;

	_side_lt_col = col_mod( side, 1.015f );
	_side_dk_col = col_mod( side,  .985f );
}

//==================================================================
// NOTE: using fixed point in the inner loop here could be important for
// slowwer machines
void road::draw_patch(
			graphics &g,
			float x1a,
			float x2a,
			float ya,
			float x1b,
			float x2b,
			float yb,
			int road_col,
			float edge_w_coe,
			int edge_col,
			float line_w_coe,
			int draw_line )
{
	float dy = yb - ya + 1;
	if ( dy <= 0 )
		return;

	float	delta_l = 0;
	float delta_r = 0;
	// speedup
	if ( dy > 1 )
	{
		delta_l = (x1b - x1a) / dy;
		delta_r = (x2b - x2a) / dy;
	}

	int yai = (int)floorf( ya );
	int ybi = (int)floorf( yb );
	for (int y=yai; y < ybi; ++y)
	{
		float w = x2a - x1a;

		float edge_w = (w * edge_w_coe) * 4;
		float line_w = (w * line_w_coe) * 4;

		float x = x1a;

		g.line_h2( x			, edge_w		, y, edge_col );
		g.line_h2( x+w-edge_w	, edge_w		, y, edge_col );
		g.line_h2( x+edge_w		, w-edge_w*2	, y, road_col );

		if ( draw_line != 0 && line_w > (1.0f/16) )
		{
			float	subW = w / 5;

			if ( line_w >= 1.0f )
			{
				float xx = x + subW + edge_w/2 - line_w/2;

				for (int i=0; i < 4; ++i)
				{
					g.line_h2( xx, (int)line_w, y, _line_col );
					xx += subW;
				}
			}
			else
			{
				float xx = x + subW + edge_w/2;

				for (int i=0; i < 4; ++i)
				{
					g.line_h2( (int)xx, 1, y, _line_mip_col );
					xx += subW;
				}
			}
		}

		x1a += delta_l;
		x2a += delta_r;
	}
}

//==================================================================
void road::prepare_vehicle_list( DVec<vehicle*> &v_list, int pos_base )
{
	pos_base -= 200;
	if ( pos_base < 0 )
		pos_base = 0;

	int pos_min = pos_base;
	int pos_max = pos_base + _strip_h * VIEW_PATCHES;

	for (size_t i=0; i < v_list.size(); ++i)
		v_list[i] = NULL;

	for (size_t i=0; i < (*mppVehicles).size(); ++i)
	{
		vehicle &v = *(*mppVehicles)[i];

		if ( v.mPos >= pos_min && v.mPos < pos_max )
		{
			int j = (int)((v.mPos - pos_base) / (_strip_h/4));

			v.mpNext = v_list[j];

			v_list[j] = &v;
		}
	}
}

//==================================================================
void road::draw_vehicles(
				graphics &g,
				DVec<vehicle*> &v_list,
				int pos,
				int base_pos,
				float r_x,
				float r_y,
				bool check_collision )
{
	// $$$ HACK: whatis "4" ?!
	int i = 4 * ((pos - base_pos) / _strip_h);

	float wh = mpXFormer->_hf_wd;

	for (int ii=3; ii >= 0; --ii)
	{
		const vehicle *pV = v_list[ i + ii ];
		for (; pV != NULL; pV = pV->mpNext)
		{
			//System.out.println( "i+ii = "+(i+ii) );
			if NOT( pV->is_drawable() )
				continue;

			// position in camera-space
			Vec3	posCS(
						r_x + pV->mX,
						r_y + 0,
						pV->mPos - base_pos + mpXFormer->_screen_d );

			if ( posCS.z <= 0 )
				continue;

			// position in screen-space
			Vec3	posSS;
			if NOT( mpXFormer->Project( posCS, posSS ) )
				continue;

			int		frame = 0;

			if ( pV->mpSprt->mFramesN >= 1 )
			{
				int t = (int)(posSS.x - wh);
				frame = pV->mpSprt->mFramesN / 2;				

				if ( t <= -140 ) frame += 2;	else
				if ( t >=  140 ) frame -= 2;	else
				if ( t <=  -70 ) frame += 1;	else
				if ( t >=   70 ) frame -= 1;

				if ( frame < 0 )				  frame = 0;
				if ( frame >= pV->mpSprt->mFramesN ) frame = pV->mpSprt->mFramesN-1;
			}

			if ( check_collision && pV->mCheckCollision )
			{
				if ( posCS.x >= -60 && posCS.x <=  60 )
				{
					float tf = fabsf( posCS.x / 60.0f );

					mpCollidedVehicle = pV;
					mCollidedVehicleStrength = 1.0f - tf;
				}
			}

			pV->draw( g, frame, (int)posSS.x, (int)posSS.y, posSS.z );
		}
	}	
}

//==================================================================
void road::drawSideObjs(
				graphics	&g,
				SectorList	**slist_array,
				SectorListIter	*pIter,
				int obj_posi,
				float org_x,
				float obj_sy,
				float obj_zz,
				u_int flags
			) const
{
	for (int j=0; j < MAX_SIDE_SPRT; ++j)
	{
		const SectorList &slist = *slist_array[j];

		int cs = pIter[j].find_prev( obj_posi-j*200 );

		if ( cs < 0 )
			continue;

		if ( slist.is_drawable( cs, obj_posi-j*200 ) )
		{
			float obj_off_x = slist.GetOffX( cs );
			float obj_sx = mpXFormer->xform_xf( org_x + obj_off_x );

			sprite *pSprt = (sprite *)slist.get_object( cs );
			pSprt->draw( g, 0, obj_sx, obj_sy, obj_zz * 2, flags );
		}
	}
}

//==================================================================
static float calcAngSinLerp( int ang0Deg, int ang1Deg, float angNormRad, float t )
{
	float ang0Sin = sinf( angNormRad * ang0Deg );
	float ang1Sin = sinf( angNormRad * ang1Deg );

	return D::Lerp( ang0Sin, ang1Sin, t );
}

//==================================================================
bool road::sector_draw( graphics &g, DVec<vehicle*> &v_list, int pos )
{
	int	sectorsN = mpoRdSec->mSectors.size();

	int		endBasePos = mpoRdSec->get_base( sectorsN - 1 );

	int		tmp = (endBasePos-1-VIEW_PATCHES) * 100;

	bool	end_reached = ( pos >= tmp );

	if ( end_reached )
		pos = pos % tmp;

	int zoff = pos % _strip_h;

	int x = 0;
	int z = -zoff;
	int last_sect = 0;
	int cur_sect = 0;

	int sy = 0;
	mMinYPosSS = INT_MAX;

	SectorList	*rd = mpoRdSec.get();
	SectorList	**ls = mpSprtsLSec;
	SectorList	**rs = mpSprtsRSec;

	SectorListIter	iterRd( *rd );
	SectorListIter	iterSpriteL[ MAX_SIDE_SPRT ];
	SectorListIter	iterSpriteR[ MAX_SIDE_SPRT ];
	for (int i=0; i < MAX_SIDE_SPRT; ++i)
	{
		iterSpriteL[i].Init( *ls[i] );
		iterSpriteR[i].Init( *rs[i] );
	}

	int posi = pos;
	//posi = (pos/100) * 100;

	float xacc = 0;
	float yacc = 0;
	float old_xacc = 0;
	float old_yacc = 0;

	float zoff_t = (float)zoff / _strip_h;
	for (int i=0; i <= VIEW_PATCHES; ++i, posi += 100)
	{
		float viewLenNorm = (float)i / VIEW_PATCHES;
		float viewLenNormToRad = viewLenNorm * GR_TO_RAD;

		cur_sect = iterRd.find_next( posi );
		if ( cur_sect < 0 )
		{
			cur_sect = 0;
			pos = 0;
			//return;
		}
		for (int j=0; j < MAX_SIDE_SPRT; ++j)
		{
			iterSpriteL[j].find_next( posi );
			iterSpriteR[j].find_next( posi );
		}

		float ts = rd->get_coe( cur_sect, posi ) * 3;
		if ( ts > 1.0f ) ts = 1.0f;

		int ox1_ang = rd->GetAngle( cur_sect );
		int ox0_ang = cur_sect > 0 ? rd->GetAngle( cur_sect-1 ) : 0;

		float ox_sin = calcAngSinLerp( ox0_ang, ox1_ang, viewLenNormToRad, ts );

		float new_xacc = old_xacc + 5 * ox_sin;
		xacc += D::Lerp( new_xacc, old_xacc, zoff_t );
		old_xacc = new_xacc;

		if ( i == 2 )
			_cur_xacc = -100 * ox_sin;

		_road_w = _lane_w * rd->GetLanes( cur_sect );

		int fake_road_w = _road_w - i*8;

		int x1 = x - fake_road_w/2 + _x;
		int x2 = x + fake_road_w/2 + _x;
		int x0 = x1 - 800;
		int x3 = x2 + 800;

		int oy0_ang = cur_sect > 0 ? rd->GetVAngle( cur_sect-1 ) : 0;
		int oy1_ang = rd->GetVAngle( cur_sect );

		float oy_sin = calcAngSinLerp( oy0_ang, oy1_ang, viewLenNormToRad, ts );

		float new_yacc = old_yacc + 2 * oy_sin;
		yacc += D::Lerp( new_yacc, old_yacc, zoff_t );
		old_yacc = new_yacc;

		x = (int)xacc;					
		int y = (int)yacc;

		mSPatches[ i ].z	 = mpXFormer->xform_zf( z );
		mSPatches[ i ].xs[0] = mpXFormer->xform_xf( x0 );
		mSPatches[ i ].xs[1] = mpXFormer->xform_xf( x1 );
		mSPatches[ i ].xs[2] = mpXFormer->xform_xf( x2 );
		mSPatches[ i ].xs[3] = mpXFormer->xform_xf( x3 );
		mSPatches[ i ].y	 = mpXFormer->xform_yf( y );

		// HACK !! record here the far sector Y,
		// but this should instead be calculated
		// regardless of the rendering
		mFarSectorY = mSPatches[ i ].y;

		mWPatches[ i ].xs[0] = x0;
		mWPatches[ i ].xs[1] = x1;
		mWPatches[ i ].xs[2] = x2;
		mWPatches[ i ].xs[3] = x3;
		mWPatches[ i ].y = y;

		z += _strip_h;

		if ( mSPatches[ i ].y < mMinYPosSS )
			mMinYPosSS = mSPatches[ i ].y;
	}

	int		oldx=0;

	int edge_col_idx = (pos / _strip_h) & 1;
	int draw_line = 3 - ((pos / _strip_h) & 3);

	int farrSx0 = mSPatches[ VIEW_PATCHES ].xs[0];
	int farrSx1 = mSPatches[ VIEW_PATCHES ].xs[1];
	int farrSx2 = mSPatches[ VIEW_PATCHES ].xs[2];
	int farrSx3 = mSPatches[ VIEW_PATCHES ].xs[3];
	int farrSy  = mSPatches[ VIEW_PATCHES ].y;
	int oldhe = 10;

	mpCollidedVehicle = NULL;

	for (int i=VIEW_PATCHES; i >= 0; --i)
	{
		float viewLenNorm = (float)i / VIEW_PATCHES;

		posi -= 100;
		cur_sect = iterRd.find_prev( posi );
		if ( cur_sect < 0 )
		{
			cur_sect = 0;
			pos = 0;
		}

		int nearSx0 = mSPatches[ i ].xs[0];
		int nearSx1 = mSPatches[ i ].xs[1];
		int nearSx2 = mSPatches[ i ].xs[2];
		int nearSx3 = mSPatches[ i ].xs[3];
		int nearSy  = mSPatches[ i ].y;

		color	edge_col;
		color	road_col;
		color	side_col;

		float coe = viewLenNorm;// * 0.9f;
		if ( (edge_col_idx++ & 1) == 0 )
		{
			edge_col = color::make_inpt(  _edge_b_col, _fog_col, coe );
			road_col = color::make_inpt( _road_col_lt, _fog_col, coe );
			//side_col = color::make_inpt( _side_lt_col, _fog_col, coe );
			side_col = _side_lt_col;
		}
		else
		{
			edge_col = color::make_inpt(  _edge_a_col, _fog_col, coe );
			road_col = color::make_inpt( _road_col_dk, _fog_col, coe );
			//side_col = color::make_inpt( _side_dk_col, _fog_col, coe );
			side_col = _side_dk_col;
		}

		// ------------------- road --------------------
		int he = (int)nearSy - (int)farrSy + 1;

		if ( (he != oldhe || nearSy != farrSy) && farrSy <= nearSy )
		{
			// draw the sides color (one rect high as the patch and from 0 to screen width - 1)
			g.rect_fill( 0, (int)farrSy, mScreenWd, (int)(nearSy-farrSy) + 1, side_col );

			// draw the road patch (a trapezoid)
			draw_patch( g,
				farrSx1, farrSx2, farrSy,
				nearSx1, nearSx2, nearSy,
				road_col,
				0.010f * .25f,
				edge_col,
				0.010f * .25f,
				mpoRdSec->is_drawable2( cur_sect, posi ) ? 1 : 0 ); // edge_col_idx&1 );
		}
		++draw_line;

		// --------------- side objects ----------------
		int obj_i = i + 2;
		int obj_posi = posi;// + 200;
		if ( obj_i <= 110 && obj_i > 0 )
		{
			float obj_x1 = mWPatches[ obj_i ].xs[1];
			float obj_x2 = mWPatches[ obj_i ].xs[2];
			float obj_y  = mWPatches[ obj_i ].y;
			float obj_zz = mSPatches[ obj_i ].z;

			mpXFormer->zz_setf( obj_zz );

			float obj_sy = mpXFormer->xform_yf( obj_y );

			drawSideObjs(
				g,
				ls,
				iterSpriteL,
				obj_posi,
				obj_x1,
				obj_sy,
				obj_zz,
				sprite::FLIP_X_FLG
			);

			drawSideObjs(
				g,
				rs,
				iterSpriteR,
				obj_posi,
				obj_x2,
				obj_sy,
				obj_zz,
				0
			);
		}

		// ------------------ vehicles --------------------
		x = mWPatches[i].xs[1] + mWPatches[i].xs[2] >> 1;
		int y = mWPatches[i].y;

		if ( i < VIEW_PATCHES-2 )
		{
			int		xx = (int)D::Lerp( x, oldx, zoff_t );

			draw_vehicles( g, v_list, posi, pos, xx, y, (i >= 2 && i <= 4) );
		}
		oldx = x;

		farrSx0 = nearSx0;
		farrSx1 = nearSx1;
		farrSx2 = nearSx2;
		farrSx3 = nearSx3;
		farrSy  = nearSy;
		oldhe = he;
	}

	return end_reached;
}

//==================================================================
int road::edge_collision( int x, int y, int z )
{
	int	roadHW = _road_w >> 1;
	int rEdge = _x + roadHW;
	int lEdge = _x - roadHW;

	if ( rEdge < x ) return x - rEdge;
	if ( lEdge > x ) return x - lEdge;

	return 0;
}

//==================================================================
const vehicle *road::collided_vehicle() const
{
	return mpCollidedVehicle;
}

//==================================================================
float road::collided_vehicle_strength()
{
	return mCollidedVehicleStrength;
}

//==================================================================
void road::vehicles_set( DVec<vehicle*> &pList )
{
	mppVehicles = &pList;
}